// ==UserScript==
// @name         动态按关注分组过滤
// @namespace    https://bbs.tampermonkey.net.cn/
// @version      0.1.0
// @description  动态信息按照分组组别过滤，选择后下拉刷新即可
// @author       口吃者
// @match        https://t.bilibili.com/*
// @require      https://scriptcat.org/lib/2691/1.0.0/sweetalert2.all.min-11.15.10.js
// @run-at       document-body
// @grant        unsafeWindow
// @license MIT
// ==/UserScript==

(function () {
    'use strict';
    class RequestMonitor {
        constructor() {
            // 预编译正则表达式提升匹配性能
            this._patterns = new Map(); // Map<RegExp, Processor>

            // 使用WeakMap防止内存泄漏
            this.pendingRequests = new WeakMap();

            // 缓存已验证的URL
            this._urlCache = {
                monitored: new Set(),
                ignored: new Set()
            };

            // 性能统计
            this.stats = {
                total: 0,
                processed: 0,
                errors: 0
            };

            this._initHooks();
        }
        /* pattern:正则表达式(只要满足其一就会进行监听处理)，processor：处理器函数(对满足的每一个pattern进行processor处理，一对多) */
        registerHandler(pattern, processor) {
            const regex = new RegExp(pattern);
            this._patterns.set(regex, processor);
            return this; // 支持链式调用
        }

        _initHooks() {
            // 保存原生方法引用
            const nativeFetch = unsafeWindow.fetch;
            const nativeXHROpen = XMLHttpRequest.prototype.open;
            const nativeXHRSend = XMLHttpRequest.prototype.send;

            const self = this;

            // Hook XHR open方法捕获URL
            XMLHttpRequest.prototype.open = function (method, url) {
                this._requestURL = url;
                return nativeXHROpen.apply(this, arguments);
            };

            // Hook XHR send
            XMLHttpRequest.prototype.send = function (body) {
                const xhr = this;
                const url = xhr._requestURL || '';

                // 前置检查
                if (!self._shouldMonitor(url)) {
                    return nativeXHRSend.apply(this, arguments);
                }

                const requestKey = {};
                self.pendingRequests.set(xhr, requestKey);
                self.stats.total++;

                // 添加事件监听
                xhr.addEventListener('load', () => self._handleXHRResponse(xhr, requestKey));
                xhr.addEventListener('error', () => self._cleanup(xhr));

                nativeXHRSend.apply(this, arguments);
            };

            // Hook Fetch
            unsafeWindow.fetch = async (input, init) => {
                const url = typeof input === 'string' ? input : input.url;

                if (!self._shouldMonitor(url)) {
                    return nativeFetch(input, init);
                }

                const requestKey = {};
                self.pendingRequests.set(requestKey, true);
                self.stats.total++;

                try {
                    const response = await nativeFetch(input, init);
                    /* 
                        Response对象有一个重要特性：​​其body流（stream）只能被读取一次​。
                        ​避免直接消费response，导致原有网页代码报错，消费response.clone()
                     */
                    self._handleFetchResponse(url, response.clone(), requestKey);
                    return response;
                } catch (error) {
                    self.stats.errors++;
                    self._cleanup(requestKey);
                    throw error;
                }
            };
        }

        _shouldMonitor(url) {
            if (this._urlCache.monitored.has(url)) return true;
            if (this._urlCache.ignored.has(url)) return false;

            for (const [regex] of this._patterns) {
                if (regex.test(url)) {
                    this._urlCache.monitored.add(url);
                    return true;
                }
            }

            this._urlCache.ignored.add(url);
            return false;
        }

        async _handleXHRResponse(xhr, requestKey) {
            try {
                const data = await this._parseResponse(xhr.response);
                await this._processData(xhr._requestURL, data);
                this.stats.processed++;
            } catch (error) {
                this.stats.errors++;
                console.error('XHR处理失败:', error);
            } finally {
                this._cleanup(xhr);
            }
        }

        async _handleFetchResponse(url, response, requestKey) {
            try {
                const data = await this._parseResponse(await response.text());
                await this._processData(url, data);
                this.stats.processed++;
            } catch (error) {
                this.stats.errors++;
                console.error('Fetch处理失败:', error);
            } finally {
                this._cleanup(requestKey);
            }
        }

        async _processData(url, rawData) {
            for (const [regex, processor] of this._patterns) {
                if (regex.test(url)) {
                    await processor(rawData);
                    break; // 仅匹配第一个处理器
                }
            }
        }

        async _parseResponse(data) {
            try {
                return JSON.parse(data);
            } catch {
                return data;
            }
        }

        _cleanup(target) {
            this.pendingRequests.delete(target);

            // 定期清理缓存防止内存增长
            if (this._urlCache.monitored.size > 1000) {
                this._urlCache.monitored.clear();
            }
        }

        // 调试方法
        printStats() {
            console.table({
                '总请求数': this.stats.total,
                '已处理请求': this.stats.processed,
                '当前挂起请求': this.pendingRequests.size,
                '错误数量': this.stats.errors,
                '缓存命中率': `${(this.stats.processed / this.stats.total * 100).toFixed(1)}%`
            });
        }
    }

    class FollerGroup {
        constructor() {
            this.groups = {}; // 使用对象存储分组，键是分组tagid，值是包含uid的数组
            this.groupTagIdMap = {}
        }

        // 添加新分组
        addGroup(groupName) {
            if (typeof groupName !== 'string' || groupName.trim() === '') {
                throw new Error('分组名称不能为空或无效');
            }
            if (!this.groups[groupName]) {
                this.groups[groupName] = [];
            }
        }

        // 向指定分组添加博主
        addUserToGroup(groupName, uid) {
            if (!this.groups[groupName]) {
                throw new Error(`分组 ${groupName} 不存在`);
            }
            if (!this.groups[groupName].includes(uid)) {
                this.groups[groupName].push(uid);
            }
        }

        addGroupTagIdMap(groupTagId, groupName) {
            this.groupTagIdMap[groupTagId] = groupName;
        }

        // 从指定分组移除博主
        removeUserFromGroup(groupName, uid) {
            const group = this.groups[groupName];
            if (group) {
                const index = group.indexOf(uid);
                if (index !== -1) {
                    group.splice(index, 1);
                }
            }
        }

        // 移除整个分组
        removeGroup(groupName) {
            delete this.groups[groupName];
        }

        // 获取所有分组名称
        getGroups() {
            return Object.keys(this.groups);
        }

        // 获取指定分组的所有博主uid
        getUsersInGroup(groupName) {
            return this.groups[groupName] || [];
        }

        // 获取所有分组的详细信息（分组名及其中的uid列表）
        getGroupsDetails() {
            return this.groups;
        }

        // 获取所有分组的详细信息（分组名及其中的uid列表）
        getGroupTagIdMap() {
            return this.groupTagIdMap;
        }

        extractUniqueUserIds() {
            const dictionary = this.groups;
            const uniqueUserIds = new Set();
            //只处理对象本身拥有的属性，而不是从其原型链继承的属性
            for (const key in dictionary) {
                if (dictionary.hasOwnProperty(key)) {
                    const userIds = dictionary[key];
                    userIds.forEach(userId => uniqueUserIds.add(userId));
                }
            }
            return Array.from(uniqueUserIds);
        }

        // 新增方法：将 groups 中的每个键对应的数组重置为空列表
        resetAllGroups() {
            for (const key in this.groups) {
                if (this.groups.hasOwnProperty(key)) {
                    this.groups[key] = [];
                }
            }
        }
    }
    const follerGroup = new FollerGroup();
    const monitorTest = new RequestMonitor();
    monitorTest.registerHandler('web-dynamic/v1/feed/all', async data => {
        const dynamicHtmlArray = document.querySelectorAll("div.bili-dyn-list > div.bili-dyn-list__items > div");
        const saveItems = follerGroup.extractUniqueUserIds();
        if(saveItems.length <= 0){
            return;
        }
        if(dynamicHtmlArray){
            dynamicHtmlArray.forEach(item=>{
                if(item.style.display == 'none'){
                    return;
                }
                //防止懒加载报错
                if(!item.querySelector("div.bili-dyn-title > span")){
                    return;
                }
                if(!saveItems.includes(item.querySelector("div.bili-dyn-title > span").innerText)){
                    item.style.display = "none";
                }
            })
        }
    });
    window.addEventListener('load', addPanel);
    window.addEventListener('load', async () => {
        var result01 = await fetchUserFollowerGroup();
        for (var result01ItemIndex in result01['data']) {
            var result01Item = result01['data'][result01ItemIndex];
            if (result01Item["tagid"] == 0) {
                continue;
            }
            follerGroup.addGroupTagIdMap(result01Item['tagid'].toString(), result01Item['name']);
            follerGroup.addGroup(result01Item['tagid'].toString());
        }
        console.log(follerGroup)
    })

    async function fetchUserFollowerGroup(comment_params) {
        const response = await fetch("https://api.bilibili.com/x/relation/tags", {
            headers: {
                'origin': 'https://www.bilibili.com'
            },
            credentials: 'include'  // 明确指定携带cookies
        });
        return await response.json();
    }

    async function fetchGroupDetail(comment_params) {
        const response = await fetch(`https://api.bilibili.com/x/relation/tag?tagid=293381761`, {
            headers: {
                'origin': 'https://www.bilibili.com'
            },
            credentials: 'include'  // 明确指定携带cookies
        });
        return await response.json();
    }
    // Your code here...
    function addPanel() {
        function genButton(text, foo, id, fooParams = {}) {
            let b = document.createElement('button');
            b.textContent = text;
            b.style.verticalAlign = 'inherit';
            // 使用箭头函数创建闭包来保存 fooParams 并传递给 foo
            b.addEventListener('click', () => {
                foo.call(b, ...Object.values(fooParams)); // 使用 call 方法确保 this 指向按钮对象
            });
            if (id) { b.id = id };
            return b;
        }


        function generateGroupCheckboxes(groupTagIdMap) {
            let html = '';
            for (const groupId in groupTagIdMap) {
                const groupName = groupTagIdMap[groupId];
                html += `
              <label class="swal2-checkbox" style="display: flex;">
                  <input type="checkbox" name="myGroup" id="swal2-checkbox-${groupId}" value="${groupId}">
                  <span class="swal2-label">${groupName}</span>
              </label>
              `;
            }
            return html.trim();
        }

        function getSelectedValues() {
            const checkboxes = document.querySelectorAll('#swal2-html-container .swal2-checkbox input:checked');
            if (!checkboxes) {
                return [];
            }
            const selectedValues = Array.from(checkboxes).map(cb => cb.value);
            return selectedValues;
        }

        async function openPanelFunc() {
            var swalRangeValue = 0;
            var initHtml = `<span>|ω・）请等待网页加载完成~</span>`;
            const { value: formValues } = await Swal.fire({
                position: "center-end",
                draggable: true,
                backdrop: false,
                title: "<strong>Filter Dynamic</strong>",
                showCancelButton: true,
                cancelButtonText: 'Cancel',
                confirmButtonText: 'Confirm',
                //class="swal2-range" swalalert框架可能会对其有特殊处理，导致其内标签的id声明失效
                html: initHtml,
                focusConfirm: false,
                didOpen: () => {
                    document.getElementById('swal2-html-container').innerHTML = generateGroupCheckboxes(follerGroup.getGroupTagIdMap());


                },
                willClose: () => {

                },
                preConfirm: () => {
                    var selectedValues = getSelectedValues();
                    return [
                        selectedValues
                    ];
                }
            });
            if (formValues) {
                if (formValues[0].length <= 0) {
                    follerGroup.resetAllGroups();
                    const Toast = Swal.mixin({
                        toast: true,
                        position: "top-end",
                        showConfirmButton: false,
                        timer: 3000,
                        timerProgressBar: true,
                        didOpen: (toast) => {
                            toast.onmouseenter = Swal.stopTimer;
                            toast.onmouseleave = Swal.resumeTimer;
                        }
                    });
                    Toast.fire({
                        icon: "success",
                        title: "重置成功~"
                    });
                    return;
                }
                const fetchPromises = formValues[0].map(groupId => {
                    // 发起请求
                    return fetch(`https://api.bilibili.com/x/relation/tag?tagid=${groupId}`, {
                        headers: {
                            'origin': 'https://www.bilibili.com'
                        },
                        credentials: 'include'  // 明确指定携带 cookies
                    })
                        .then(response => {
                            if (!response.ok) {
                                throw new Error("HTTP error " + response.status);
                            }
                            return response.json();
                        })
                        .then(data => {
                            // 为成功的结果添加 groupId 作为标识符
                            return {
                                groupId,
                                data
                            };
                        })
                        .catch(error => {
                            // 为失败的结果添加 groupId 作为标识符
                            return {
                                groupId,
                                error
                            };
                        });
                });
                Promise.all(fetchPromises).then(
                    fetchResultArray => {
                        fetchResultArray.forEach(result => {
                            if (result.error) {
                                console.error(`Request for groupId ${result.groupId} failed:`, result.error);
                            } else {
                                // console.log(`Result for groupId ${result.groupId}:`, result.data);
                                const resultArray = result.data.data;
                                follerGroup.resetAllGroups();
                                // 如果 data 存在且长度大于 0，执行 addUserToGroup
                                if (resultArray && resultArray.length > 0) {
                                    resultArray.forEach(item =>
                                        follerGroup.addUserToGroup(result.groupId.toString(), item["uname"]));
                                }
                            }
                        })
                        const dynamicHtmlArray = document.querySelectorAll("div.bili-dyn-list > div.bili-dyn-list__items > div");
                        const saveItems = follerGroup.extractUniqueUserIds();
                        if(dynamicHtmlArray){
                            dynamicHtmlArray.forEach(item=>{
                                if(item.style.display == 'none'){
                                    return;
                                }
                                //防止懒加载报错
                                if(!item.querySelector("div.bili-dyn-title > span")){
                                    return;
                                }
                                if(!saveItems.includes(item.querySelector("div.bili-dyn-title > span").innerText)){
                                    item.style.display = "none";
                                }
                            })
                        }
                        const Toast = Swal.mixin({
                            toast: true,
                            position: "top-end",
                            showConfirmButton: false,
                            timer: 3000,
                            timerProgressBar: true,
                            didOpen: (toast) => {
                                toast.onmouseenter = Swal.stopTimer;
                                toast.onmouseleave = Swal.resumeTimer;
                            }
                        });
                        Toast.fire({
                            icon: "success",
                            title: "过滤成功~"
                        });
                    }
                )
            }
        }

        let myButton = genButton('Filter', openPanelFunc, 'Filter');
        document.body.appendChild(myButton);

        var css_text = `
        #Filter {
            position: fixed;
            color: #FF6B35; /* 橙色活力主题主色 */
            top: 70%;
            right: -20px; /* 初始右侧隐藏 */
            transform: translateY(-50%);
            z-index: 1000;
            padding: 10px 24px;
            border-radius: 5px;
            cursor: pointer;
            border: 0;
            background-color: white;
            box-shadow: rgba(0 0 0 / 5%) 0 0 8px;
            letter-spacing: 1.5px;
            text-transform: uppercase;
            font-size: 9px;
            transition: all 0.5s ease;
        }
        #Filter:hover {
            right: 0%; /* 鼠标悬停时完整显示 */
            letter-spacing: 3px;
            background-image: linear-gradient(to top, #FFD700 0%, #FFA080 100%); /* 橙色渐变 */
            box-shadow: rgba(255, 107, 0, 0.7) 0px 7px 29px 0px; /* 橙色阴影 */
        }
        
        #Filter:active {
            letter-spacing: 3px;
            background-image: linear-gradient(to top, #FFD700 0%, #FFA080 100%);
            box-shadow: rgba(255, 107, 0, 0.5) 0px 0px 0px 0px;
            transition: 100ms;
        }

        /* 固定复选框容器高度并添加滚动条 */
        #swal2-html-container {
            max-height: 200px;  /* 可根据需求调整高度 */
            overflow-y: auto;    /* 垂直滚动条 */
            overflow-x: hidden;  /* 隐藏水平滚动条 */
            padding-right: 15px; /* 防止滚动条遮挡内容 */
            border: 1px solid #ddd; /* 可选：添加边框 */
        }

        /* 可选：优化滚动条样式（仅现代浏览器支持） */
        #swal2-html-container::-webkit-scrollbar {
            width: 8px; /* 滚动条宽度 */
        }

        #swal2-html-container::-webkit-scrollbar-track {
            background: #f1f1f1;
        }

        #swal2-html-container::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 4px;
        }

        #swal2-html-container::-webkit-scrollbar-thumb:hover {
            background: #555;
        }

        /* 基础样式 */
        #swal2-html-container .swal2-checkbox {
            padding: 8px 12px; /* 增加内边距以便悬停效果更明显 */
            transition: background-color 0.2s ease; /* 平滑过渡效果 */
        }

        /* 鼠标悬停时的背景色 */
        #swal2-html-container .swal2-checkbox:hover {
            background-color: #e0e0e0; /* 浅灰色 */
        }

        /* 可选：已选中时的样式（可选） */
        #swal2-html-container .swal2-checkbox input:checked ~ .swal2-label {
            color: #2E8B57; /* 选中后的文字颜色（可自定义） */
        }

        `;
        GMaddStyle(css_text);
    }

    function GMaddStyle(css) {
        var myStyle = document.createElement('style');
        myStyle.textContent = css;
        var doc = document.head || document.documentElement;
        doc.appendChild(myStyle);
    }
})();